#!/usr/bin/ruby

# == Synopsis 
#   Get and set some svn properties on files & directories inside a given path.
#
# == Usage 
#   svn_props [options] path
#
# == Options
#   -h, --help          Displays help message
#
#   --dir-props         Shows directory properties
#   --missing-ids       Shows files with missing id keyword
#   --set-ids           Sets ids on files lacking them

require 'optparse' 
require 'ostruct'
require 'rdoc/usage'

# Store dirs & files inside a hash table. 
#
# Takes a dir path & a dir mask as initialization arguments:
# DirTree.new(path = '.', mask = ['.', '..', '.svn'])
#
# Entries being part of the mask will be omitted.
# Dirs are read recursively.

class DirTree
  
  attr_reader :dir_mask, :tree
  
  protected
  
  # Push keys & values to @tree.
  #
  # For every non-existend key an empty array will be created to hold the values. 
  
  def push_to_tree(key, value = nil)
    
    @tree[key] = [] if !@tree.has_key?(key)
    @tree[key].push(value) if value
    
  end
  
  # Recursivley walk a given dir.
  #
  # Calls push_to_tree to store dir paths & containing files.
  # Evaluates @dir_mask before doing so.
  
  def walk_dir(dir)
    
    # Store dir
    push_to_tree(dir)   
    # Loop dir          
    Dir.foreach(dir){|entry|
      if !@dir_mask.include?(entry)
        # Build full path, otherwise File.directory? won't work as expected 
        entry = File.join(dir, entry)
        if File.directory?(entry)
          walk_dir(entry)
        else
          push_to_tree(dir, entry)
        end
      end
    }
    
  end
  
  # Init
  #
  # Expects a string a an array as arguments.
  
  def initialize(dir, dir_mask)
    @tree = {}
    @dir_mask = dir_mask
    walk_dir(dir) 
  end 
end

# Gets & sets svn properties for files & directories.
#
# Gets all properties for directories, fetches missing id properties for files & sets them, if ask to. 

class SVNProps
  
  @files
  @dirs
  @props
  @file_include_mask
  
  attr_reader :props
  
  protected
  
  # Issue svn command
  #
  # == Params
  # String cmd
  
  def svn_cmd(cmd)
    `svn #{cmd}`
  end
  
  def indicate_busy(indicator = '.')
    print indicator
  end
  
  public
  
  # Get dir properties by evaluating svn proplist. Writes results to @props['Dir props'].
  #
  # return Array
  
  def get_dir_props
    
    @dirs.each{|dir|
      indicate_busy
      svn = svn_cmd("proplist #{dir}").to_a
      svn.each{|result|
        if result.strip! =~ /^(svn:[a-z]+)$/i
        @props['Dir props'][dir] = {} if !@props['Dir props'].has_key?(dir)
        @props['Dir props'][dir].store($1, svn_cmd("propget #{$1} #{dir}").gsub(/(\r)|(\n)/, '').to_a)
        end
      } 
    }
    
  end

  def pretty_print_dir_props
    
    msg = 'Directory properties'
    puts "\n"*2, msg
    puts '='*msg.length
    puts
    @props['Dir props'].each_pair{|dir, props|
      puts dir
      puts '-'*dir.length
      props.each_pair{|prop, propval|
        puts "[#{prop}]", propval
      }
      puts 
    }
    
  end
    
  # Gets files without id property by evaluating svn svn:keywords. Writes results to @props['Missing Ids'].
  #
  # return Array
  
  def get_missing_id_props
    
    @files.each{|file|
      indicate_busy
      svn = svn_cmd("propget svn:keywords #{file}")
      if svn !~ /\bId\b/ 
        @props['Missing Ids'].push(file)
      end 
    }
    puts
    puts @props['Missing Ids']
      
  end
  
  # Sets svn:keywords 'Id' to files by evaluating @props['Missing Ids']. Writes results to @props['Set Ids'].
  # Calls get_missing_id_props if @props['Missing Ids'] happens to be empty.     
  #
  # return Array
  
  def set_id_props
  end
  
  # == Params
  # Array dirs 
  # Array files 
  # Array file_mask 
  
  def initialize(dirs, files, file_include_mask = [])
    @dirs = dirs
    @files = files
    @file_include_mask = file_include_mask
    @props = {'Dir props' => {}, 'Missing Ids' => [], 'Set Ids' => []}
  end
end

# Main
#
# Parses options & evaluates what to do

class App
  
  def initialize(arguments)
    @arguments = arguments
    # Set defaults
    @options = OpenStruct.new
    @options.show_dir_props = true
  end

  # Parse options, check arguments, then process the command
  def run
        
    if parsed_options? && arguments_valid? 
      process_arguments            
      process_command
    else
      output_help
    end
      
  end
  
  protected
  
  def parsed_options?
    
    # Specify options
    opts = OptionParser.new 
    opts.on('-h', '--help') { output_help }
    opts.on('--dir-props')  { @options.show_dir_props = true }  
    opts.on('--missing-ids'){ @options.show_missing_ids = true }
    
    opts.parse!(@arguments) rescue return false
    
    process_options
    true      
  end

  # Performs post-parse processing on options
  def process_options
    @options.verbose = false if @options.quiet
  end
  
  # True if required arguments were provided
  def arguments_valid?
    # TO DO - implement your real logic here
    true if @arguments.length == 1 
  end
  
  # Setup the arguments
  def process_arguments
    # TO DO - place in local vars, etc
  end
  
  def output_help
    RDoc::usage() #exits app
  end
  
  def process_command
    # TO DO - do whatever this app does
    
    #process_standard_input # [Optional]
  end
end

if $0 == __FILE__

  Dir.chdir(File.dirname(__FILE__))

  app = App.new(ARGV)
  app.run
    
  dir = DirTree.new('../App/library', ['.', '..', '.svn', 'Zend'])
  svn_props = SVNProps.new(dir.tree.keys, dir.tree.values.flatten, ['php', 'ini', 'xml', 'txt'])    

=begin    
  puts
  print 'Reading directory properties'
  svn_props.get_dir_props
  svn_props.pretty_print_dir_props
    
  puts
  print 'Evaluating file id properties'
  svn_props.get_missing_id_props
=end

end

